<?php
namespace wechat\component;

use wechat\component\ErrorCode;
use wechat\component\PKCS7Encoder;


/**
 * Prpcrypt class
 *
 * 提供接收和推送给公众平台消息的加解密接口.
 * 2020-10-20  鲁纪章 兼容 openssl crypt and decrypt 修正
 */
class Prpcrypt
{
	public $key;

	function __construct($k)
	{
		$this->key = base64_decode($k . "=");
	}

	/**
	 * 随机生成16位字符串
	 * @return string 生成的字符串
	 */
	function getRandomStr()
	{

		$str = "";
		$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
		$max = strlen($str_pol) - 1;
		for ($i = 0; $i < 16; $i++) {
			$str .= $str_pol[mt_rand(0, $max)];
		}
		return $str;
    }
    

    /**
	 * 对明文进行加密
	 * @param string $input 需要加密的明文
	 * @return string 加密后的密文
	 */
    public function encrypt($text = '',$appid) {

		try {
			//获得16位随机字符串，填充到明文之前
			$random = $this->getRandomStr();
			$text = $random . pack("N", strlen($text)) . $text . $appid;
			$iv = substr($this->key, 0, 16);
			
			if(function_exists("openssl_encrypt")){
				// openssl_encrypt($str, 'aes-128-cbc', $key, true, $iv)
				// !!!此处只有 AES-256-ECB 和 OPENSSL_RAW_DATA 才能和下文输出一样的结果 ，并注意 key 和 iv 都要使用
				// $encrypted = @openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA,$iv);
				// $encrypted = @openssl_encrypt($text, 'AES-128-CBC', $iv, OPENSSL_RAW_DATA);
				$encrypted = @openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA ,$iv);

			}
			else{

				// 网络字节序
				$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
				$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
				
				//使用自定义的填充方式对明文进行补位填充
				$pkc_encoder = new PKCS7Encoder;
				$text = $pkc_encoder->encode($text);
				mcrypt_generic_init($module, $this->key, $iv);
				//加密
				$encrypted = mcrypt_generic($module, $text);
				mcrypt_generic_deinit($module);
				mcrypt_module_close($module);
			}
			$encrypted = base64_encode($encrypted);
			//使用BASE64对加密后的字符串进行编码
			return array(ErrorCode::$OK, $encrypted);
		} catch (\Exception $e) {
			//print $e;
			return array(ErrorCode::$EncryptAESError, null);
		}
    }
    

    /**
	 * 对密文进行解密
	 * @param string $encrypted 需要解密的密文
	 * @return string 解密得到的明文
	 */
    public function decrypt($encrypted,$appid) {

        // echo $encrypted."\n";die('test');
		// var_dump('decrypt',$this->key);
		//使用BASE64对需要解密的字符串进行解码
		$ciphertext_dec = base64_decode($encrypted);
		// var_dump($ciphertext_dec);die('++');
		try {
			if(function_exists("openssl_decrypt")){
				
				$iv = substr($this->key, 0, 16);
				// $decrypted = openssl_decrypt($ciphertext_dec,'AES-256-CBC',$this->key,OPENSSL_RAW_DATA,$iv);
				// 这里必须用 OPENSSL_NO_PADDING 解密，不然可能会有部分数据无法解密 
				// var_dump('zero='.OPENSSL_ZERO_PADDING,'no='.OPENSSL_NO_PADDING,'raw='.OPENSSL_RAW_DATA,'pkcs='.OPENSSL_PKCS1_PADDING,'--',OPENSSL_NO_PADDING===(OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING));
				// OPENSSL_NO_PADDING===(OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING)
				$decrypted = openssl_decrypt($ciphertext_dec,'AES-256-CBC',$this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING ,$iv);
				// var_dump($decrypted);
			}
			else{
				$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
				$iv = substr($this->key, 0, 16);
				mcrypt_generic_init($module, $this->key, $iv);

				//解密
				$decrypted = mdecrypt_generic($module, $ciphertext_dec);
				
				mcrypt_generic_deinit($module);
				mcrypt_module_close($module);
			}
			
			if(empty($decrypted)){
				return array(ErrorCode::$DecryptEmpty, null);
			}
		} catch (\Exception $e) {
			return array(ErrorCode::$DecryptAESError, null);
		}
		
		try {
			//去除补位字符
			$pkc_encoder = new PKCS7Encoder;
			$result = $pkc_encoder->decode($decrypted);
			//去除16位随机字符串,网络字节序和AppId
			// 这里原版SDK有Bug 一旦解密不成功返回 $decrypted===false ,$result===false 原文返回空字符串，会导致result[0] 非法数组索引异常
			if (strlen($result) < 16)
				return array(ErrorCode::$ResultLengthLT16, null);

			$content = substr($result, 16, strlen($result));
			$len_list = unpack("N", substr($content, 0, 4));
			$xml_len = $len_list[1];
			$xml_content = substr($content, 4, $xml_len);
			$from_appid = substr($content, $xml_len + 4);
		} catch (\Exception $e) {
			return array(ErrorCode::$IllegalBuffer, null);
		}
		
		if ($from_appid != $appid)
			return array(ErrorCode::$ValidateAppidError, null);
		
		return array(0, $xml_content);
    }

}

?>